<template>
  <div v-show="mode === 'debug'">
    <h1>Scatter plot - {{ chart.dr_method }}</h1>
    <h2 v-show="chart.status.loading">Loading data...</h2>
  </div>

  <v-row>
    <v-col :cols="8">
      <figure style="position: relative">
        <svg ref="chart_ref"></svg>

        <div class="tool-bar">
          <v-tooltip location="top">
            <template v-slot:activator="{ props: tooltip }">
              <v-btn
                class="tool-bar-btn"
                v-bind="tooltip"
                icon="mdi-home"
                @click="handleResetZoom"
              />
            </template>
            <span>恢复缩放</span>
          </v-tooltip>

          <v-menu
            v-show="PANEL_STYLE === ('btn_pop' as typeof PANEL_STYLE)"
            :close-on-content-click="false"
          >
            <template v-slot:activator="{ props: menu }">
              <v-tooltip location="top">
                <template v-slot:activator="{ props: tooltip }">
                  <v-btn
                    v-show="PANEL_STYLE === ('btn_pop' as typeof PANEL_STYLE)"
                    class="tool-bar-btn"
                    v-bind="mergeProps(menu, tooltip)"
                    icon="mdi-cog"
                  />
                </template>
                <span>绘图设置</span>
              </v-tooltip>
            </template>

            <ControlPanel
              v-show="PANEL_STYLE === ('btn_pop' as typeof PANEL_STYLE)"
              v-model:dr_method="chart.dr_method"
              v-model:status="chart.status"
              v-model:size="chart.size"
              v-model:show_axis="chart.show_axis"
              :handle_reset_zoom="handleResetZoom"
              :draw_graph="drawGraph"
              :calculate_then_draw="calculateThenDraw"
              :handle_change_circle_size="handleChangeCircleSize"
            />
          </v-menu>
        </div>
      </figure>
    </v-col>

    <v-col :cols="4">
      <ControlPanel
        v-show="PANEL_STYLE === ('block' as typeof PANEL_STYLE)"
        v-model:dr_method="chart.dr_method"
        v-model:status="chart.status"
        v-model:size="chart.size"
        v-model:show_axis="chart.show_axis"
        :handle_reset_zoom="handleResetZoom"
        :draw_graph="drawGraph"
        :calculate_then_draw="calculateThenDraw"
        :handle_change_circle_size="handleChangeCircleSize"
      />
    </v-col>
  </v-row>

  <ToolTip ref="tooltip_ref" />
</template>

<script setup lang="ts">
// import types
import { PropType } from 'vue'
import { ParagraphDRData } from '@/types/visualization/graph'
import { DataSource, DRMethod } from '@/types/visualization/enum.ts'

// import native modules
import * as d3 from 'd3'
import { reactive, ref, onMounted, watch, watchEffect, mergeProps } from 'vue'
import { MessagePlugin } from 'tdesign-vue-next'

// import corresponding components
import { getParaType } from './utils.ts'

import ToolTip from '@/components/vis-graph/figure/ToolTip.vue'
import { calcTooltipLocation } from '@/components/vis-graph/figure/utils.ts'

import ControlPanel from './scatter-control-panel.vue'

import { getEmbeddings } from '@/components/vis-graph/data/L5-application'

// 控制面板样式：按钮弹出 | 块级
const PANEL_STYLE: 'btn_pop' | 'block' = 'block'

const props = defineProps({
  mode: {
    type: String as PropType<'release' | 'debug'>,
    required: false,
    default: 'release',
  },
  ready: {
    type: Boolean,
    required: true,
    default: false,
  },
})

const chart_ref = ref<SVGSVGElement | null>(null)
const tooltip_ref = ref<InstanceType<typeof ToolTip> | null>(null)

const chart = reactive({
  status: {
    loading: true,
  },

  // d3 元素句柄
  handler: {
    svg: <d3.Selection>null,
    zoom: <d3.ZoomBehavior>null,

    // 比例尺
    scale: {
      x_scale: <d3.ScaleLinear>null,
      y_scale: <d3.ScaleLinear>null,
    },

    // 坐标点图层
    dots_layers: <d3.Selection>[],
  },
  size: {
    width: 700,
    height: 500,
    margin: {
      default: 30,
      top: 10,
      right: 10,
      bottom: 30,
      left: 30,
    },
    // 坐标点大小
    circle: 2.5,

    // 画布位移和缩放
    transform: {
      x: 0,
      y: 0,
      k: 1,
    },
  },

  // 画布缩放限制：[最小缩放倍数, 最大缩放倍数]
  scale_limit: {
    scale: <[number, number]>[0.1, Infinity],
  },

  // 是否显示坐标轴
  show_axis: <boolean>true,

  // 与图表绑定的数据
  data: <ParagraphDRData[]>[],

  // 数据降维方法名称
  dr_method: <DRMethod>DRMethod.PCA,
})

// 缩放画布
const handleResetZoom = () => {
  chart.handler.svg
    .transition()
    .duration(500)
    .call(chart.handler.zoom.transform, d3.zoomIdentity)
}

// 计算点大小
const calcCircleSize = (d: ParagraphDRData) => {
  const r =
    (d.type === DataSource.LABELED ? (d.shared ? 1.5 : 1) : 1.2) /
    chart.size.transform.k
  return chart.size.circle * r
}

// 修改点大小
const handleChangeCircleSize = () => {
  // const size = chart.size.circle / chart.size.k
  chart.handler.svg.selectAll('circle').attr('r', calcCircleSize)
}

// 修改降维方法
const handleChangeDrMethod = (dr_method: DRMethod) => {
  console.log(`Change dr-method to ${dr_method}`)
  // chart.dr_method = dr_method
  calculateThenDraw()
}

// 绘制图表
const drawGraph = () => {
  chart.status.loading = true

  const width = chart.size.width
  const height = chart.size.height
  let marginTop = chart.size.margin.top
  let marginRight = chart.size.margin.right
  let marginBottom = chart.size.margin.bottom
  let marginLeft = chart.size.margin.left
  let data = chart.data

  if (!chart.show_axis) {
    marginTop = marginRight = marginBottom = marginLeft = 0
  }

  // 定义数值域与视觉域的映射
  const x_scale = d3
    .scaleLinear()
    .domain(d3.extent(data, (d) => d.embedding.x))
    .nice()
    .range([marginLeft, width - marginRight])

  const y_scale = d3
    .scaleLinear()
    .domain(d3.extent(data, (d) => d.embedding.y))
    .nice()
    .range([height - marginBottom, marginTop])

  // 获取提示框
  const tooltip = tooltip_ref.value?.$el

  // 获取绘图区域
  const svg = d3.select(chart_ref.value)

  // 重绘前清空画布
  svg.selectAll('*').remove()

  svg
    .attr('preserveAspectRatio', 'xMinYMin meet')
    .attr('viewBox', `0 0 ${width} ${height}`)
  // .attr('style', 'max-width: 100%; height: auto; font: 10px sans-serif;')

  // Create the axes.
  const x_axis = d3.axisBottom(x_scale)
  const y_axis = d3.axisLeft(y_scale)

  let x_group: d3.Selection | null = null
  let y_group: d3.Selection | null = null

  if (chart.show_axis) {
    x_group = svg
      .append('g')
      .attr('transform', `translate(0, ${height - marginBottom})`)
      .call(x_axis)
      .call((g) => g.select('.domain').remove())

    y_group = svg
      .append('g')
      .attr('transform', `translate(${marginLeft}, 0)`)
      .call(y_axis)
      .call((g) => g.select('.domain').remove())
  }

  const draw_x_grid = (g, scale: d3.ScaleLinear) =>
    g
      .append('g')
      .selectAll('line')
      .data(scale.ticks())
      .join('line')
      .attr('x1', (d) => scale(d))
      .attr('x2', (d) => scale(d))
      .attr('y1', marginTop)
      .attr('y2', height - marginBottom)

  const draw_y_grid = (g, scale: d3.ScaleLinear) =>
    g
      .append('g')
      .selectAll('line')
      .data(scale.ticks())
      .join('line')
      .attr('y1', (d) => scale(d))
      .attr('y2', (d) => scale(d))
      .attr('x1', marginLeft)
      .attr('x2', width - marginRight)

  // Create the grid.
  const grid = svg
    .append('g')
    .attr('stroke', 'currentColor')
    .attr('stroke-opacity', 0.1)
    .call((g) => draw_x_grid(g, x_scale))
    .call((g) => draw_y_grid(g, y_scale))

  // Add a layer of dots.
  const dots = svg
    .append('g')
    // .attr('stroke', 'steelblue')
    // .attr('stroke-width', 1)    .attr('fill', '#2e66bf')
    .selectAll('circle')
    .data(data)
    .join('circle')
    .attr('cx', (d) => x_scale(d.embedding.x))
    .attr('cy', (d) => y_scale(d.embedding.y))

    // 如果标注数据中的点被匹配上，则标注为绿色，并放大
    .attr('r', calcCircleSize)
    .attr('fill', (d) =>
      // Green: #19c919
      d.type === DataSource.EXTRACTED ? '#f00' : d.shared ? '#04c7e1' : '#000',
    )
    .on('mouseover', (event, d) => {
      tooltip.style.visibility = 'visible'
      tooltip_ref.value?.setTooltipData({
        title: d.mof,
        data: {
          ...d.params,
          ...d.embedding,
        },
      })
    })
    .on('mousemove', (event) => {
      const new_location = calcTooltipLocation(tooltip, event)
      tooltip.style.left = `${new_location.left}px`
      tooltip.style.top = `${new_location.top}px`
    })
    .on('mouseout', (event) => {
      tooltip.style.visibility = 'hidden'
    })

  // 缩放处理函数
  const handleZoom = (e) => {
    // 保存放大倍数
    chart.size.transform.x = e.transform.x
    chart.size.transform.y = e.transform.y
    chart.size.transform.k = e.transform.k

    // 缩放坐标点
    dots.attr('transform', d3.zoomTransform(svg.node()))
    handleChangeCircleSize()

    // 缩放坐标轴
    if (chart.show_axis) {
      x_group
        .call(x_axis.scale(e.transform.rescaleX(x_scale)))
        .call((g) => g.select('.domain').remove())
      y_group
        .call(y_axis.scale(e.transform.rescaleY(y_scale)))
        .call((g) => g.select('.domain').remove())
    }

    // 缩放网格
    grid.selectAll('g').remove()
    grid
      .call((g) => draw_x_grid(g, e.transform.rescaleX(x_scale)))
      .call((g) => draw_y_grid(g, e.transform.rescaleY(y_scale)))
  }

  // 创建画布缩放逻辑
  const zoom = d3
    .zoom()
    .scaleExtent(chart.scale_limit.scale)
    .translateExtent([
      [-width, -height],
      [2 * width, 2 * height],
    ])
    .duration(500)
    .on('start', () => {
      svg.style('cursor', 'move')
    })
    .on('zoom', handleZoom)
    .on('end', () => {
      svg.style('cursor', 'default')
    })

  svg.call(zoom)

  // 保存 d3 元素句柄 并 更新状态
  chart.handler = {
    svg,
    scale: {
      x_scale,
      y_scale,
    },
    zoom,
    dots_layers: [dots],
  }

  chart.status.loading = false
}

// 重新计算
const calculateThenDraw = async () => {
  chart.status.loading = true

  const data = await getEmbeddings(chart.dr_method)
  if (data.length === 0) {
    await MessagePlugin.error({ content: '数据为空', duration: 3000 })
    return
  }

  data.sort((a, b) => getParaType(a) - getParaType(b))

  chart.data = data
  drawGraph()

  chart.status.loading = false
}

// 初始化图表
const initGraph = () => {
  calculateThenDraw()
}

defineExpose({
  initGraph,
})

watchEffect(() => {
  if (props.ready) {
    console.log('🎨 Update ScatterPlot')
    initGraph()
  }
})
</script>

<style lang="less" scoped>
.tool-bar {
  position: absolute;
  top: 0;
  right: 0;

  & .tool-bar-btn {
    margin: 0 0.3rem;
    opacity: 0.5;
    &:hover {
      opacity: 1;
    }
  }
}
</style>
